-- Set some header names that Blizzard left out:
local CustomHeaders = {
BINDING_HEADER_MULTICASTFUNCTIONS = "Totem Bar - Call of the Elements",
BINDING_HEADER_BLANK = "Totem Bar - Call of the Ancestors",
BINDING_HEADER_BLANK2 = "Totem Bar - Call of Spirits",
BINDING_HEADER_BLANK3 = "Totemic Recall",
BINDING_HEADER_MULTIACTIONBAR = "MultiActionBar BottomLeft",
BINDING_HEADER_BLANK4 = "MultiActionBar BottomRight",
BINDING_HEADER_BLANK5 = "MultiActionBar Right",
BINDING_HEADER_BLANK6 = "MultiActionBar Right 2",
}

-- Util functions
function GetTableLength(t)
	local count = 0
	for _ in pairs(t) do
		count = count + 1
	end
	return count
end

local function AlphabeticalSort(a,b)
	return a:upper() < b:upper()
end

local tmptable = {}
local function GetTableEntryByNumber(t, index)
	if tmptable[0] ~= tostring(t) or #tmptable ~= GetTableLength(t) then
		local nextEntry = 1
		tmptable[0] = tostring(t)
		for k,v in pairs(t) do
			if v then
				tmptable[nextEntry] = k
				nextEntry = nextEntry + 1
			end
		end
		
		for i=nextEntry, #tmptable do
			tmptable[i] = nil
		end
		
		table.sort(tmptable, AlphabeticalSort)
	end
	return tmptable[index], t[tmptable[index]]
end

local KeyBindingFrame = KeyBindingFrame
local HeadersFrame = CreateFrame("Frame", nil, KeyBindingFrame)
HeadersFrame:SetPoint("TOPLEFT", -200, 0)
HeadersFrame:SetWidth(256)
HeadersFrame:SetHeight(504)

local topleft = HeadersFrame:CreateTexture(nil, "BACKGROUND")
topleft:SetWidth(128)
topleft:SetHeight(256)
topleft:SetTexture([[Interface\KeyBindingFrame\UI-KeyBindingFrame-TopLeft]])
topleft:SetPoint("TOPLEFT")

local topright = HeadersFrame:CreateTexture(nil, "BACKGROUND")
topright:SetWidth(128)
topright:SetHeight(256)
topright:SetTexture([[Interface\KeyBindingFrame\UI-KeyBindingFrame-TopRight]])
topright:SetPoint("TOPRIGHT")

local bottomleft = HeadersFrame:CreateTexture(nil, "BACKGROUND")
bottomleft:SetWidth(128)
bottomleft:SetHeight(248)
bottomleft:SetTexture([[Interface\KeyBindingFrame\UI-KeyBindingFrame-TopLeft]])
bottomleft:SetTexCoord(0,1,1,0)
bottomleft:SetPoint("BOTTOMLEFT")

local bottomright = HeadersFrame:CreateTexture(nil, "BACKGROUND")
bottomright:SetWidth(128)
bottomright:SetHeight(248)
bottomright:SetTexture([[Interface\KeyBindingFrame\UI-KeyBindingFrame-TopRight]])
bottomright:SetTexCoord(0,1,1,0)
bottomright:SetPoint("BOTTOMRIGHT")

local WarningFrame = CreateFrame("Frame", "BBF_WarningFrame", KeyBindingFrame)
WarningFrame:SetPoint("TOP", KeyBindingFrameScrollFrame, "BOTTOM", 20, -80)
WarningFrame:SetWidth(560)
WarningFrame:SetHeight(80)
WarningFrame:SetBackdrop({bgFile = "Interface/DialogFrame/UI-DialogBox-Background", 
	edgeFile = "Interface/DialogFrame/UI-DialogBox-Border", 
	tile = true, tileSize = 16, edgeSize = 16, 
	insets = { left = 4, right = 4, top = 4, bottom = 4 }})

KeyBindingFrame:HookScript("OnShow", function()
	if InCombatLockdown() then
		WarningFrame:Show()
	else
		WarningFrame:Hide()
	end
end)

WarningFrame:SetScript("OnEvent", WarningFrame.Show)
WarningFrame:RegisterEvent("PLAYER_REGEN_DISABLED")

local WarningHeader = WarningFrame:CreateTexture(nil, "ARTWORK")
WarningHeader:SetTexture("Interface/DialogFrame/UI-DialogBox-Header")
WarningHeader:SetPoint("CENTER", WarningFrame, "TOP", 0, -12)
WarningHeader:SetWidth(200)
WarningHeader:SetHeight(64)

local HeaderText = WarningFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
HeaderText:SetPoint("CENTER", WarningFrame, "TOP")
HeaderText:SetText("Warning!")

local TaintWarning = WarningFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
TaintWarning:SetPoint("TOPLEFT", 9, -9)
TaintWarning:SetPoint("BOTTOMRIGHT", -9, 9)
TaintWarning:SetText("Please be aware that using the shortcuts provided by BBF to scroll down the bindings list causes taint. When used in combat, this prevents you from changing bindings. If you want to use the shortcuts |cffffffffin combat, be sure to scroll down or up after clicking|r them to clear the taint!")
TaintWarning:SetTextColor(1,0,0)

local headers = {}
local bindings = {}

local numbindings
local function PopulateHeaders()	
	local _G = _G
	local GetBinding = GetBinding
	
	local binding, name
	local isHeader
	numbindings = GetNumBindings()
	for i=1, numbindings do
		binding = GetBinding(i)
		isHeader = binding:find("HEADER")
		
		if isHeader then
			name = CustomHeaders["BINDING_"..binding] or _G["BINDING_"..binding]
		else
			name = _G["BINDING_NAME_"..binding]
		end
		
		if not name or name == " " then
			name = binding:sub(binding:find("CLICK") and 7 or 8)
		end
		
		if name and name:find("%S") then
			if isHeader then
				headers[name] = i
			end
			bindings[i] = name
		end
	end
end
	
local HeadersScrollFrame = CreateFrame("ScrollFrame", "BetterBindingFrame_HeadersScrollFrame", HeadersFrame, "FauxScrollFrameTemplate")
HeadersScrollFrame:SetPoint("TOPLEFT", HeadersFrame, "TOPLEFT", 12, -53)
HeadersScrollFrame:SetPoint("BOTTOMRIGHT", HeadersFrame, "BOTTOMRIGHT", -78, 51)
HeadersScrollFrame.table = headers

local search = CreateFrame("EditBox", "BBF_SearchEditBox", HeadersFrame, "InputBoxTemplate")
search:SetFontObject("GameFontNormalSmall")
search:SetTextColor(1,1,1)
search:SetWidth(80)
search:SetHeight(12)
search:SetPoint("TOPLEFT",16,-33)
search:SetAutoFocus(false)

search.title = search:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
search.title:SetPoint("TOPLEFT", 0, 12)
search.title:SetText("Search")

local headersbutton = CreateFrame("Button", "BBF_HeadersButton", HeadersFrame, "UIPanelButtonTemplate2")
headersbutton:SetPoint("TOPRIGHT", -54, -28)
headersbutton:SetWidth(80)

headersbutton.text = BBF_HeadersButtonText
headersbutton.text:SetText("Headers")
headersbutton:SetScript("OnClick", function(self)
	search:SetText("")
	FauxScrollFrame_SetOffset(HeadersScrollFrame, 0)
	HeadersScrollFrame.mode = "headers"
	HeadersScrollFrame.table = headers
	HeadersScrollFrame:update()
end)

-- 65-90 upper, 97-122 lower case.
local CharLookupTable
local char, byte = string.char, string.byte
local function CreateCharLookupTable()
	-- Return a . for characters not in the latin alphabet.
	CharLookupTable = setmetatable({}, {__index = function() return "." end})
	for i=1, 26 do
		CharLookupTable[i] = string.format("[%s%s]", char(i+64), char(i+96))
	end
end

local function GetCharacterPattern(character)
	if not CharLookupTable then
		CreateCharLookupTable()
	end
	
	local cByte = byte(character)
	if cByte >= 97 and cByte <= 122 then
		cByte = cByte - 32
	end
	
	return CharLookupTable[cByte-64]
end

local result
search:SetScript("OnEnterPressed", function(self)
	self:ClearFocus()
	
	local query = self:GetText()
	if query == "" then
		return
	end
	
	-- Convert query to pattern that matches both upper and lower case of each letter.
	query = query:gsub(".", GetCharacterPattern)

	result = {}
	for i=1, #bindings do
		if bindings[i]:find(query) then
			result[#result+1] = i
		end
	end

	FauxScrollFrame_SetOffset(HeadersScrollFrame, 0)
	HeadersScrollFrame.mode = "query"
	HeadersScrollFrame.table = result
	HeadersScrollFrame:update()
end)

local btnHeight = 16
local numButtons = math.floor((KeyBindingFrame:GetHeight()-104)/btnHeight)
local width = HeadersScrollFrame:GetWidth()


local scrollframe = KeyBindingFrameScrollFrame
local scrollbar = KeyBindingFrameScrollFrameScrollBar
local function OnClick(self, button)
	scrollbar:SetValue((self.offset-1)*KEY_BINDING_HEIGHT)
	FauxScrollFrame_SetOffset(scrollframe, self.offset-1)
	KeyBindingFrame_Update()
	local dad = self:GetParent()
	if dad.previous and dad.previous < numButtons and dad.previous >= 1 then
		HeadersScrollFrame["button"..dad.previous].text:SetTextColor(1,1,1)
	end
	dad.previous = self.id
	dad.set = self.text:GetText()
	self.text:SetTextColor(1,1,0)
end

local previous
for i=1, numButtons do
	--Button
	frame = CreateFrame("Button",nil,HeadersFrame)
	frame:SetPoint("TOPLEFT", previous or HeadersFrame, previous and "BOTTOMLEFT" or "TOPLEFT", previous and 0 or 12, previous and 0 or -53)
	frame:SetHeight(btnHeight)
	frame:SetWidth(width)
	frame:RegisterForClicks("LeftButtonUp")
	frame:SetScript("OnClick", OnClick)
	
	--Button text.
	frame.text = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	frame.text:SetAllPoints()
	frame.text:SetJustifyH("LEFT")
	frame.id = i
	previous = frame
	HeadersScrollFrame["button"..i] = frame
end

HeadersScrollFrame.update = function(self)
	local currenttable = self.table
	local offset = FauxScrollFrame_GetOffset(self)
	local lineplusoffset, tablelength
	local parent = self:GetParent()
	local buttonname, name, bindingoffset
	local mode = self.mode
	
	if mode == "query" then
		tablelength = #currenttable
	else
		tablelength = GetTableLength(currenttable)
	end
	
	FauxScrollFrame_Update(self, tablelength, numButtons, btnHeight)
	
	for line = 1, numButtons do
		buttonname = "button"..line
		lineplusoffset = line + offset
		if lineplusoffset <= tablelength then
			if mode == "query" then
				bindingoffset = currenttable[lineplusoffset]
				name = bindings[bindingoffset]
			else
				name, bindingoffset = GetTableEntryByNumber(currenttable, lineplusoffset)
			end
			
			self[buttonname].text:SetText(name)
			self[buttonname].offset = bindingoffset
			self[buttonname]:Show()
			if parent.set == name then
				self[buttonname].text:SetTextColor(1,1,0)
			else
				self[buttonname].text:SetTextColor(1,1,1)
			end
		else
			self[buttonname]:Hide()
		end
	end
end

HeadersScrollFrame:SetScript("OnShow", function(self) self:update() end)
HeadersScrollFrame:SetScript("OnVerticalScroll", function(self, offset) 
	self.lastoffset = self.offset
	FauxScrollFrame_OnVerticalScroll(self, offset, btnHeight, self.update)
	if HeadersFrame.previous then
		HeadersFrame.previous = HeadersFrame.previous - (self.offset-self.lastoffset)
	end
end)

HeadersScrollFrame:SetScript("OnEvent", function(self)
	if not numbindings or GetNumBindings() > numbindings then
		PopulateHeaders()
		if self:IsShown() then
			self:update()
		end
	end
end)
HeadersScrollFrame:RegisterEvent("ADDON_LOADED")